package main

import (
	"bufio"
	"crypto/tls"
	"database/sql"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"mock/cert"
	"mock/proxy"
	"mock/utils"
	"net/http"
	"os"
	"regexp"
	"strings"
	"time"

	_ "github.com/mattn/go-sqlite3"
	uuid "github.com/satori/go.uuid"
	"gopkg.in/yaml.v2"
)

//定义全局的sql对象
var gdb *sql.DB

//定义全局的hash映射
var hash = make(map[string][]table)

//全局配置文件内容
var Gconfig Config

//初始化
func init() {

	//定义日志文件
	f, err := os.OpenFile("mock.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		panic(err)
	}
	//定义日志格式,prefix为日志行首的信息，暂时为空吧，没啥信息
	// mocklog = log.New(f, "", log.Ldate|log.Ltime|log.Lshortfile)
	log.SetOutput(f)
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	//定义本地sqlite存储
	db, err := sql.Open("sqlite3", "routes.db")
	if err != nil {
		log.Fatalln(err)
	}
	gdb = db
	_, err = gdb.Exec("create table routes (id integer PRIMARY KEY autoincrement,uuid char(36) not null unique, path char(1024) not null,match string char(36),status  integer,method char(12) ,response_code int,response_header TEXT,response_data TEXT)")

	if err != nil {
		switch errString := err.Error(); {
		case strings.Contains(errString, "already exists"):
			log.Println(err)
		case strings.Contains(errString, "syntax error"):
			//语法错误直接退出
			log.Fatal("exit")
		}
	}
}

//请求信息
type Request struct {
	Path   string   `json:"path"`   //请求path
	Method []string `json:"method"` //请求方法
	Match  string   `json:"match"`  //匹配模式，可能是正则
}

//响应信息
type Response struct {
	StatusCode     int                 `json:"status_code"`     //响应code
	ResponseHeader []map[string]string `json:"response_header"` //响应头部
	ResponseData   interface{}         `json:"response_data"`   //响应内容
}

//请求与响应
type Rule struct {
	Uuid   string   `json:"uuid"`
	Req    Request  `json:"req"`
	Rsp    Response `json:"rsp"`
	Status int      `json:"status"` // 1 代表启用  2 代表禁用
}

//映射表，把数据库部分字段取出
type table struct {
	path   string
	uuid   string
	match  string
	status int
}

//配置文件结构
type Config struct {
	HttpAddr     string              `yaml:"http_addr"`
	HttpsAddr    string              `yaml:"https_addr"`
	Header       []map[string]string `yaml:"header"`
	GenerateYaml proxy.Generate      `yaml:"generate_yaml"`
}

func resp(w http.ResponseWriter, r *http.Request, rule_uuid string, value map[string]string) {
	var response_code int
	var response_data string
	var response_header_list []map[string]string
	var response_header_data string
	//通过uuid 获取响应数据
	err := gdb.QueryRow("select response_code,response_header,response_data from routes where uuid=?  and status=?", rule_uuid, 1).Scan(&response_code, &response_header_data, &response_data)
	if err != nil {
		log.Println(err)
	}
	//反序列化
	err = json.Unmarshal([]byte(response_header_data), &response_header_list)
	if err != nil {
		log.Println(err)
	}

	if len(response_header_list) > 0 {
		for _, header := range response_header_list {
			for k, v := range header {
				w.Header().Add(k, v)
			}
		}
	} else {
		//如果header中没有信息，默认返回
		if r.Header.Get("Content-Type") != "" {
			w.Header().Add("Content-Type", r.Header.Get("Content-Type"))
		} else {
			w.Header().Add("Content-Type", "application/json")
		}
	}

	if r.Header.Get("Origin") != "" {
		w.Header().Add("Access-Control-Allow-Origin", r.Header.Get("Origin"))
	} else {
		w.Header().Add("Access-Control-Allow-Origin", "*")
	}

	w.Header().Set("Date", http.TimeFormat)

	if r.Header.Get("Access-Control-Request-Method") != "" {
		w.Header().Set("Access-Control-Allow-Methods", r.Header.Get("Access-Control-Request-Method"))
	} else {
		w.Header().Set("Access-Control-Allow-Methods", "*")
	}

	if r.Header.Get("Access-Control-Request-Headers") != "" {
		w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers"))
	} else {
		w.Header().Set("Access-Control-Allow-Headers", "*")
	}

	w.Header().Set("Date", http.TimeFormat)

	for _, item := range Gconfig.Header {
		for k, v := range item {
			w.Header().Add(k, v)
		}
	}

	w.WriteHeader(response_code)
	//渲染数据
	test, err := template.New("test").Parse(response_data)
	if err != nil {
		log.Println(err)
	}
	err = test.Execute(w, value)
	if err != nil {
		log.Println(err)
	}
	return

}

func add(w http.ResponseWriter, r *http.Request) {
	if r.Method == "POST" {
		//读取所有body数据
		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			log.Println()
		}

		//把body数据解析到Rule struct
		var rule Rule
		err = json.Unmarshal(b, &rule)
		if err != nil {
			log.Println(err)
		}
		//把body中Rsp.ResponseData数据解析成json字符串
		data, err := json.Marshal(rule.Rsp.ResponseData)
		if err != nil {
			log.Println(err)
		}
		//把body中Rsp.ResponseHeader数据解析成json字符串
		header, err := json.Marshal(rule.Rsp.ResponseHeader)
		if err != nil {
			log.Println(err)
		}

		//一个URL可能存在多个方法,如options,post
		for _, method := range rule.Req.Method {
			uid := finds(rule.Req.Path, method)
			if uid == "" {
				stmt, err := gdb.Prepare("insert into routes (uuid,path,method,match,response_code,response_header,response_data,status) values (?,?,?,?,?,?,?,?)")
				uid := fmt.Sprintf("%s", uuid.NewV4())
				stmt.Exec(uid, rule.Req.Path, strings.ToUpper(method), strings.ToUpper(rule.Req.Match), rule.Rsp.StatusCode, string(header), string(data), rule.Status)
				if err != nil {
					log.Println(err)
				}

				hash[strings.ToUpper(method)] = append(hash[strings.ToUpper(method)], table{path: rule.Req.Path, match: rule.Req.Match, uuid: uid, status: rule.Status})
			} else {
				w.Header().Add("Content-Type", "application/json")
				w.WriteHeader(400)
				w.Write(utils.ErrRouteIsExist)
				return
			}
		}
	} else {
		w.Header().Add("Content-Type", "application/json")
		w.WriteHeader(400)
		w.Write(utils.ErrMethodNotAllowed)
		return
	}

}

func deleteRoute(w http.ResponseWriter, r *http.Request) {
	if r.Method == "DELETE" {
		uid := r.FormValue("uuid")
		stmt, err := gdb.Prepare("delete from routes where uuid=?")
		if err != nil {
			log.Println(err)
		}

		_, err = stmt.Exec(uid)
		if err != nil {
			log.Println(err)
		}

		for k, v := range hash {
			for i, item := range v {
				if item.uuid == uid {
					hash[k] = append(v[:i], v[i+1:]...)
					log.Printf("移除规则%s", uid)
				}
			}
		}
	} else {
		w.WriteHeader(400)
		w.Write(utils.ErrMethodNotAllowed)
	}

}

func update(w http.ResponseWriter, r *http.Request) {

	if r.Method == "PUT" {
		//读取所有body数据
		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			log.Println(err)
		}

		//把body数据解析到Rule struct
		var rule Rule
		err = json.Unmarshal(b, &rule)
		if err != nil {
			log.Println(err)
		}

		//把body中Rsp.ResponseData数据解析成json字符串
		data, err := json.Marshal(rule.Rsp.ResponseData)
		if err != nil {
			log.Println(err)
		}
		//把body中Rsp.ResponseHeader数据解析成json字符串
		header, err := json.Marshal(rule.Rsp.ResponseHeader)
		if err != nil {
			log.Println(err)
		}

		uid := rule.Uuid

		row := gdb.QueryRow("select path,method from routes where uuid=?", uid)
		var path string
		var method string
		err = row.Scan(&path, &method)
		if err != nil {
			//未查询到记录，返回404
			log.Println(err)
			w.Header().Add("Content-Type", "application/json")
			w.WriteHeader(404)
			w.Write(utils.ErrUuidNotExist)

		}

		for _, method := range rule.Req.Method {
			uid_hash := finds(rule.Req.Path, method) //通过finds查找，返回uuid,定义临时变量uid_hash

			if uid_hash == uid || uid_hash == "" {
				//如果返回的uid_hash与请求一致或者没返回，则表示path未冲突
				for index, item := range hash[strings.ToUpper(method)] {


					if item.uuid == uid {
						//更新数据到hash中

						hash[strings.ToUpper(method)][index] = table{path: rule.Req.Path, match: rule.Req.Match,status:rule.Status,uuid:uid}

						//更新内容到数据库中
						stmt, err := gdb.Prepare("update routes set path=? ,method=? ,match=?,response_code=?,response_header=?,response_data=?,status=? where uuid=?")
						if err != nil {
							log.Println(err)
						}
						_, err = stmt.Exec(rule.Req.Path, method, rule.Req.Match, rule.Rsp.StatusCode, header, data , rule.Status,uid)
						if err != nil {
							log.Println(err)
						}
					}
				}
			} else {
				log.Println("更新存在冲突", uid_hash)
				w.Header().Add("Content-Type", "application/json")
				w.WriteHeader(400)
				w.Write(utils.ErrRouteIsExist)
			}
		}

		//把body中Rsp.ResponseData数据解析成json字符串
		//data, err := json.Marshal(rule.Rsp.ResponseData)
		if err != nil {
			log.Println(err)
		}
		//把body中Rsp.ResponseHeader数据解析成json字符串
		//header,err:= json.Marshal(rule.Rsp.ResponseHeader)
		if err != nil {
			log.Println(err)
		}
	}

}

func get(w http.ResponseWriter, r *http.Request) {
	uid_arg := r.FormValue("uuid")
	var rows *sql.Rows

	if uid_arg != "" {
		row, err := gdb.Query("select uuid,path,match,method,response_code,response_header,response_data,status from routes where uuid=?", uid_arg)
		rows = row
		if err != nil {
			log.Println(err)
		}
	} else {
		row, err := gdb.Query("select uuid,path,match,method,response_code,response_header,response_data ,status from routes ")
		rows = row
		if err != nil {
			log.Println(err)
		}
	}

	var result []map[string]interface{}
	var uid string
	var path string
	var match string
	var method string
	var status int
	var response_code int
	var response_data string
	var response_header string
	for rows.Next() {
		v := make(map[string]interface{})
		err := rows.Scan(&uid, &path, &match, &method, &response_code, &response_header, &response_data, &status)
		if err != nil {
			log.Println(err)
		}
		v["uuid"] = uid
		v["path"] = path
		v["match"] = match
		v["method"] = method
		v["response_code"] = response_code
		v["status"] = status
		//反序列化一下response_header
		var response_header_interface interface{}
		err = json.Unmarshal([]byte(response_header), &response_header_interface)
		if err != nil {
			log.Println(err)
		}
		v["response_header"] = response_header_interface

		//反序列化一下response_data
		var response_data_interface interface{}
		err = json.Unmarshal([]byte(response_data), &response_data_interface)
		if err != nil {
			fmt.Println(err)
		}
		v["response_data"] = response_data_interface

		//把每条数据追加到返回结果中
		result = append(result, v)
	}
	w.Header().Add("Content-Type", "application/json")
	w.WriteHeader(200)
	data, err := json.Marshal(result)
	if err != nil {
		log.Println(err)
	}
	w.Write(data)
}

func walk(requesPath, rulePath []string, reg bool) (bool, map[string]string) {
	//定义一个map，用于返回参数的值
	value := make(map[string]string)
	m, n := len(requesPath), len(rulePath)
	if n > m {
		//如果规则path，比请求path长，直接返回false
		return false, nil
	} else {
		for index, item := range requesPath {
			if findreg(rulePath[index]) != "" {
				b, err := regexp.MatchString(findreg(rulePath[index]), item)
				if !b || err != nil {
					log.Println(findreg(rulePath[index]), item, b, err)
					return false, nil
				}
			} else {
				if index <= n-1 {
					//如果规则包含:开头或者等于*开头
					if strings.HasPrefix(rulePath[index], ":") || strings.HasPrefix(rulePath[index], "*") {
						//去掉左边的:
						value[strings.TrimLeft(strings.TrimLeft(rulePath[index], ":"), "*")] = item
					}
					//如果请求path，小于等于规则长，则判断是否相等，如果不相等判断规则path是否包含：，"*" ，否则返回false
					if item != rulePath[index] && (!strings.HasPrefix(rulePath[index], ":") && !strings.HasPrefix(rulePath[index], "*")) {
						return false, nil
					}
					//如果请求path，/xxx/ ,通过/分割后,第二个为空字符串，直接匹配失败
					if item == "" && rulePath[index] != "" {
						return false, nil
					}

				} else {
					//如果请求path，比规则长，判断规则path最后一个是不是包含* ，否则返回false
					if !strings.HasPrefix(rulePath[n-1], "*") {
						return false, nil
					}

				}
			}

		}
		return true, value
	}
}

func insert(requesPath, rulePath []string) bool {
	m, n := len(requesPath), len(rulePath)
	if n > m {
		//如果规则path，比请求path长，直接返回false
		return false
	} else {
		for index, item := range requesPath {
			if index <= n-1 {
				//如果请求path，小于等于规则长，则判断是否相等，如果不相等判断规则path是否包含：，"*" ，否则返回false
				if item != rulePath[index] &&
					(!strings.HasPrefix(rulePath[index], ":") && !strings.HasPrefix(rulePath[index], "*")) &&
					(!strings.HasPrefix(item, ":") && !strings.HasPrefix(item, "*")) {
					return false
				}
			} else {
				//如果请求path，比规则长，判断规则path最后一个是不是包含* ，否则返回false
				if !strings.HasPrefix(rulePath[n-1], "*") {
					return false
				}
			}
		}
		return true
	}
}

func find(path, method string) (string, map[string]string) {
	//通过方法，变量map
	log.Println("开始查找本地路由", method, path)
	var value map[string]string
	log.Println("在此hash上找", hash[strings.ToUpper(method)])
	for _, item := range hash[strings.ToUpper(method)] {
		//该路由是启用状态才进行比对
		if item.status == 1 {
			//map遍历中，如果规则为BASIC，且请求path，等于规则path则返回匹配成功的UUID
			//if strings.ToUpper(item.match)=="BASIC" && path==item.path{
			//	return item.uuid
			//}

			//if strings.ToUpper(item.match)=="WILDCARD"{
			//分割path
			reqpath := strings.Split(path, "/")
			rulepath := strings.Split(item.path, "/")
			log.Println("请求path", reqpath)
			log.Println("规则path", rulepath)

			result, value := walk(reqpath, rulepath, item.match == "reg")
			if result {
				return item.uuid, value
			}
		}

	}
	//}
	return "", value
}

func findreg(re string) string {
	//定义传递的正则为re(xxx),先去掉左边re(,再去掉右边)，中间就是我们需要的正则
	if strings.HasPrefix(re, "reg(") && strings.HasSuffix(re, ")") {
		data := strings.TrimRight(strings.TrimLeft(re, "reg("), ")")
		return data
	}
	return ""
}

func finds(path, method string) string {
	//通过方法，变量map

	for _, item := range hash[strings.ToUpper(method)] {
		//该路由是启用状态才进行比对

		//map遍历中，如果规则为BASIC，且请求path，等于规则path则返回匹配成功的UUID
		//if strings.ToUpper(item.match)=="BASIC" && path==item.path{
		//	return item.uuid
		//}
		//if strings.ToUpper(item.match)=="WILDCARD"{
		//分割path
		reqpath := strings.Split(path, "/")
		rulepath := strings.Split(item.path, "/")
		if insert(reqpath, rulepath) {
			return item.uuid
		}
	}
	//}
	return ""
}

type Pxy struct {
	cert *cert.Certificate
	tls  bool
}

// 运行代理服务
func (p *Pxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if req.Method != "CONNECT" {
		// 处理http
		p.HTTP(rw, req)
	} else {
		//糟糕的是，websocket 与https发送的都是CONNECT方法，但是websock可能未加密
		if p.tls {
			//处理https,websocket加密
			p.forwardHTTPS(req, rw)
		} else {
			//处理websocket未加密
			p.forwardws(req, rw)
		}
	}
}

func (p *Pxy) HTTP(rw http.ResponseWriter, req *http.Request) {
	//跨域问题，前端一般在请求之前会发一个option方法，此方法响应允许，前端浏览器才不会阻止后面的请求，不然有跨域问题
	//所以针对option方法，统一返回
	//r.RequestURI 会把query param打印出来，此处过滤?后面字符串 ,例如/wjs?user=jiangyd，而我需要的是/wjs
	reqPath := req.URL.Path
	dst := req.URL.Scheme + "://" + req.Host + reqPath

	switch true {
	case reqPath == "/mock/add":
		add(rw, req)
	case strings.HasPrefix(reqPath, "/mock/delete"):
		deleteRoute(rw, req)
	case reqPath == "/mock/update":
		update(rw, req)
	case reqPath == "/mock/get":
		get(rw, req)
	default:

		rule_uuid, value := find(dst, req.Method)
		//先看看本地代理有没有匹配的路径，如果有从本地返回，如果没有再到源服务器
		if rule_uuid != "" {
			log.Println("匹配到了请求,", rule_uuid)
			resp(rw, req, rule_uuid, value)
		} else {
			log.Println("未匹配到请求", dst)
			h := &proxy.HttpProxy{}
			h.Http(rw, req)
		}
	}
}
func main() {
	hash["GET"] = []table{}
	hash["POST"] = []table{}
	hash["PUT"] = []table{}
	hash["DELETE"] = []table{}
	hash["OPTIONS"] = []table{}

	data, err := ioutil.ReadFile("config.yaml")
	if err != nil {
		log.Fatalln(err)
	}
	err = yaml.Unmarshal(data, &Gconfig)
	if err != nil {
		log.Fatalln(err)
	}

	proxy.GenerateCfg = Gconfig.GenerateYaml

	log.Println("配置文件的内容", Gconfig)
	//rows, err := gdb.Query("select path,uuid,match,method,status from routes where status=?", 1)
	rows, err := gdb.Query("select path,uuid,match,method,status from routes")
	if err != nil {
		log.Println(err)
	}
	for rows.Next() {
		var path string
		var uuid string
		var match string
		var method string
		var status int
		err = rows.Scan(&path, &uuid, &match, &method, &status)
		if err != nil {
			log.Println(err)
		}

		switch strings.ToUpper(method) {
		case "GET":
			hash["GET"] = append(hash["GET"], table{path: path, uuid: uuid, match: match, status: status})
		case "POST":
			hash["POST"] = append(hash["POST"], table{path: path, uuid: uuid, match: match, status: status})
		case "PUT":
			hash["PUT"] = append(hash["PUT"], table{path: path, uuid: uuid, match: match, status: status})
		case "DELETE":
			hash["DELETE"] = append(hash["DELETE"], table{path: path, uuid: uuid, match: match, status: status})
		case "OPTIONS":
			hash["OPTIONS"] = append(hash["OPTIONS"], table{path: path, uuid: uuid, match: match, status: status})
		default:
			log.Println(method, "method not support")
		}
	}

	log.Println("hash映射内容:", hash)
	//http.Handle("/", &Pxy{})

	//这里不能也用&Pxy{},不然指针赋值会影响

	go func() {
		h := &Pxy{tls: false}
		http.HandleFunc("/proxy/file", func(writer http.ResponseWriter, request *http.Request) {
			filename := request.FormValue("name")
			if filename == "proxy.pac" || filename == "server.pem" {
				f, err := os.Open(filename)
				if err != nil {
					log.Println(err)
				}
				data, err := ioutil.ReadAll(f)
				if err != nil {
					log.Println(err)
				}
				if filename == "proxy.pac" {
					host := strings.Split(request.Host, ":")
					data = []byte(fmt.Sprintf(string(data), host[0], host[0]))
					log.Println(data)
				}
				writer.Header().Add("Content-Disposition", "attachment;filename="+filename)
				writer.Write(data)
			}
		})
		http.Handle("/", h)
		err = http.ListenAndServe(Gconfig.HttpAddr, nil)
		if err != nil {
			log.Fatalln(err)
		}
	}()
	https := &Pxy{tls: true}
	err = http.ListenAndServe(Gconfig.HttpsAddr, https)
	if err != nil {
		log.Fatalln(err)
	}

	log.Printf("http proxy server start on %s\n", Gconfig.HttpAddr)
	log.Printf("https proxy server start on %s\n", Gconfig.HttpsAddr)
}

// HTTPS,websockets转发
func (p *Pxy) forwardHTTPS(req *http.Request, rw http.ResponseWriter) {
	hij, ok := rw.(http.Hijacker)
	if !ok {
		log.Println("HTTP Server does not support hijacking")
	}
	//劫持连接
	client, brw, err := hij.Hijack()
	if err != nil {
		log.Println(err)
		return
	}

	defer client.Close()
	//握手信息
	_, err = client.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))

	//为请求host生成证书
	tlsConfig, err := p.cert.Generate(req.URL.Host)
	if err != nil {
		log.Println(err)
		return
	}
	//转成tls连接
	tlsClientConn := tls.Server(client, tlsConfig)
	tlsClientConn.SetDeadline(time.Now().Add(30 * time.Second))
	defer tlsClientConn.Close()
	if err := tlsClientConn.Handshake(); err != nil {
		log.Println(err)
		return
	}
	buf := bufio.NewReader(tlsClientConn)
	tlsReq, err := http.ReadRequest(buf)

	if err != nil {
		if err != io.EOF {
			log.Println(err)
		}
		return
	}
	tlsReq.RemoteAddr = req.RemoteAddr
	tlsReq.URL.Scheme = "https"
	tlsReq.URL.Host = tlsReq.Host
	req = tlsReq
	if strings.Contains(req.Header.Get("Connection"), "Upgrade") && strings.Contains(req.Header.Get("Upgrade"), "websocket") {
		//wss连接
		newReq := new(http.Request)
		*newReq = *req
		newReq.URL.Scheme = "wss"
		w := proxy.WebsocketProxy{Backend: newReq.URL}
		w.ForwardWebsocket(rw, newReq, tlsClientConn, brw)

	} else {
		//https连接
		h := &proxy.HttpProxy{}
		h.Https(req, tlsClientConn)
	}
}

// websocket 转发，只处理未加密的websocket
func (p *Pxy) forwardws(req *http.Request, rw http.ResponseWriter) {
	hij, ok := rw.(http.Hijacker)
	if !ok {
		log.Println("HTTP Server does not support hijacking")
	}
	//劫持连接
	client, brw, err := hij.Hijack()
	if err != nil {
		log.Println(err)
		return
	}
	defer client.Close()
	//握手信息
	_, err = client.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
	buf := bufio.NewReader(client)
	wsreq, err := http.ReadRequest(buf)

	if wsreq.Method == "GET" && strings.Contains(wsreq.Header.Get("Connection"), "Upgrade") && strings.Contains(wsreq.Header.Get("Upgrade"), "websocket") {
		newReq := new(http.Request)
		*newReq = *wsreq
		newReq.URL.Scheme = "ws"
		w := proxy.WebsocketProxy{Backend: newReq.URL}
		w.ForwardWebsocket(rw, newReq, client, brw)
	} else {
		return
	}
}
